package main

import (
	"bufio"
	"fmt"
	"github.com/logoove/cli"
	"io"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"
	"xdb/xdb"
)

func main() {
	app := cli.NewApp()
	app.Name = "xdb"
	app.Version = "1.0.0"
	app.Authors = "Yoby\nWechat:logove"
	app.Description = "ip2region管理工具"
	app.Usage = "支持IP数据生成，测试，修改"
	app.SeeAlso = "2021-" + strconv.Itoa(time.Now().Year())
	app.BuildInfo = &cli.BuildInfo{
		GitBranch:   "1.0.0正式版",
		GitCommit:   "免费版",
		GitRevCount: "支持操作ip数据源",
		Timestamp:   time.Now().Format("2006-01-02 15:04:05"),
	}
	app.Commands = []*cli.Command{
		{
			Name:     "gen",
			Usage:    "根据txt库生成ip2region.xdb库",
			Examples: "xdb gen ip.merge.txt",
			Action: func(c *cli.Context) {
				var srcFile string
				dstFile := GetPath() + "/ip2region.xdb"
				var indexPolicy = xdb.BTreeIndexPolicy
				if c.NArg() == 0 {
					srcFile = GetPath() + "/ip.merge.txt"
				} else {
					srcFile = c.Args()[0]
				}
				maker, _ := xdb.NewMaker(indexPolicy, srcFile, dstFile)
				maker.Init()
				fmt.Println("开始生成,请等待...")
				maker.Start()
				maker.End()
				fmt.Println("生成完成")
			},
		},
		{
			Name:     "ip",
			Usage:    "输入ip地址，查询信息",
			Examples: "xdb ip 222.247.220.86",
			Action: func(c *cli.Context) {
				var ip string
				if c.NArg() == 0 {
					ip = Getip()
				} else {
					ip = c.Args()[0]
				}
				dstFile := GetPath() + "/ip2region.xdb"
				searcher, _ := xdb.NewSearcher(dstFile)
				defer func() {
					searcher.Close()
				}()
				ip1, _ := xdb.CheckIP(ip)
				region, _, _ := searcher.Search(ip1)
				fmt.Printf("\x1b[0;32m %s", region)
			},
		},
		{
			Name:     "test",
			Usage:    "测试生成文件正确性",
			Examples: "xdb test",
			Action: func(c *cli.Context) {
				dstFile := GetPath() + "/ip2region.xdb"
				searcher, _ := xdb.NewSearcher(dstFile)
				defer func() {
					searcher.Close()
				}()
				var ignoreError = false
				srcFile := GetPath() + "/ip.merge.txt"
				handle, _ := os.OpenFile(srcFile, os.O_RDONLY, 0600)
				var count, errCount, tStart = 0, 0, time.Now()
				var scanner = bufio.NewScanner(handle)
				scanner.Split(bufio.ScanLines)
				n := 1 //第几行
				for scanner.Scan() {
					var l = strings.TrimSpace(strings.TrimSuffix(scanner.Text(), "\n"))
					var ps = strings.SplitN(l, "|", 3)
					if len(ps) != 3 {
						fmt.Printf("invalid ip segment line `%s`\n", l)
						return
					}

					sip, err := xdb.CheckIP(ps[0])
					if err != nil {
						fmt.Printf("check start ip `%s`: %s\n", ps[0], err)
						return
					}

					eip, err := xdb.CheckIP(ps[1])
					if err != nil {
						fmt.Printf("check end ip `%s`: %s\n", ps[1], err)
						return
					}

					if sip > eip {
						fmt.Printf("start ip(%s) should not be greater than end ip(%s)\n", ps[0], ps[1])
						return
					}

					fmt.Printf("%d try to bench segment: `%s`\n", n, l)
					mip := xdb.MidIP(sip, eip)
					for _, ip := range []uint32{sip, xdb.MidIP(sip, mip), mip, xdb.MidIP(mip, eip), eip} {
						fmt.Printf("|-try to bench ip '%s' ... ", xdb.Long2IP(ip))
						region, _, err := searcher.Search(ip)
						if err != nil {
							fmt.Printf("failed to search ip '%s': %s\n", xdb.Long2IP(ip), err)
							return
						}

						// check the region info
						count++
						if region != ps[2] {
							errCount++
							fmt.Printf(" --[Failed] (%s != %s)\n", region, ps[2])
							if ignoreError == false {
								return
							}
						} else {
							fmt.Printf(" --[Ok]\n")
						}
					}
					n++

				}
				fmt.Printf("Bench finished, {count: %d, failed: %d, took: %s}\n", count, errCount, time.Since(tStart))
			},
		},
		/*{
			Name:     "add",
			Usage:    "添加ip地址到数据源，然后重新生成，已存在不做操作，n负数则是追加到末尾",
			Examples: `xdb add --ip="103.216.40.0|103.216.43.255|中国|0|北京市|北京市|电信" --n=123`,
			Flags: []*cli.Flag{
				{
					Name: "ip",
				},
				{
					Name: "n",
				},
			},
			Action: func(c *cli.Context) {
				ip := c.GetString("ip")
				n := c.GetInt("n")
				f, _ := os.OpenFile("ip.merge.txt", os.O_RDONLY, 0666)
				defer f.Close()
				scanner := bufio.NewScanner(f)
				var linearr []string
				for scanner.Scan() {
					line := strings.Replace(scanner.Text(), "\\n", "", -1)
					linearr = append(linearr, line)
				}
				if IsContain(linearr, ip) == false {
					if n == -1 {
						linearr = insertSlice(linearr, len(linearr), ip)
					} else {
						linearr = insertSlice(linearr, n, ip)
					}
					f1, _ := os.OpenFile("ip.merge.txt", os.O_CREATE, 0666)
					defer f1.Close()
					writer := bufio.NewWriter(f1)
					for _, v := range linearr {
						writer.WriteString(strings.TrimSpace(v) + "\n")
					}
					writer.Flush()

					fmt.Println("添加成功")
				} else {
					fmt.Println("已存在,共计", len(linearr), "行")
				}

			},
		},*/
		{
			Name:     "so",
			Usage:    "根据提供的ip搜索数据所在行,判断是否正确,不提供ip将自动获取",
			Examples: "xdb so 222.247.220.1",
			Action: func(c *cli.Context) {
				var ips string
				if c.NArg() == 0 {
					ips = Getip()
				} else {
					ips = c.Args()[0]
				}
				ips1 := strings.Split(ips, ".")
				fmt.Println("你提供的IP:", ips)
				ip1 := ips1[0] + "." + ips1[1] + "." + ips1[2] + ".0"
				ip2 := ips1[0] + "." + ips1[1] + ".0.0"
				ip3 := ips1[0] + "." + ips1[1] + "." + ips1[2] + ".255"
				ip4 := ips1[0] + "." + ips1[1] + ".255.255"
				srcFile := GetPath() + "/ip.merge.txt"
				handle, _ := os.OpenFile(srcFile, os.O_RDONLY, 0600)
				var scanner = bufio.NewScanner(handle)
				n := 1
				for scanner.Scan() {
					if strings.Contains(scanner.Text(), ip1) {
						fmt.Println(ip1, ",第", n, "行: ", scanner.Text())

					}
					if strings.Contains(scanner.Text(), ip2) {

						fmt.Println(ip2, ",第", n, "行: ", scanner.Text())

					}
					if strings.Contains(scanner.Text(), ip3) {
						fmt.Println(ip3, ",第", n, "行: ", scanner.Text())

					}
					if strings.Contains(scanner.Text(), ip4) {
						fmt.Println(ip4, ",第", n, "行: ", scanner.Text())

					}
					n++
				}
				fmt.Printf("参考:https://www.ip138.com/iplookup.asp?ip=%s&action=2 然后根据这个数据，可以添加到数据表", ips)
			},
		},
		{
			Name:     "del",
			Usage:    "删除某行数据,需要先用so找出,程序会自动备份一个初始文件.",
			Examples: "xdb del 100",
			Action: func(c *cli.Context) {
				n := 0
				if c.NArg() == 0 {
					n = -1
				} else {
					n, _ = strconv.Atoi(c.Args()[0])
				}
				if n < 0 {
					fmt.Println("要删除行必须大于等于0")
				}
				srcFile := GetPath() + "/ip.merge.txt"
				CopyFile(srcFile, GetPath()+"/ip.merge-backup.txt")
				handle, _ := os.OpenFile(srcFile, os.O_RDONLY, 0600)
				var scanner = bufio.NewScanner(handle)
				m := 1
				var linearr []string
				for scanner.Scan() {
					if m == n {
						fmt.Println(n, "行找到:", scanner.Text())
					}
					line := scanner.Text()
					linearr = append(linearr, line)

					m++
				}
				n = n - 1
				fmt.Println(n+1, "行确认找到: ", linearr[n])
				linearr = append(linearr[:n], linearr[n+1:]...)
				handle1, _ := os.OpenFile(GetPath()+"/ip.merge.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
				defer handle1.Close()
				writer := bufio.NewWriter(handle1)
				for _, v := range linearr {
					writer.WriteString(strings.TrimSpace(v) + "\n")
				}
				writer.Flush()
				fmt.Println("修改完成!")
			},
		},
	}
	app.Run(os.Args)
}
func Getip() string {
	netInterfaces, err := net.Interfaces()
	if err != nil {
		fmt.Println("net.Interfaces failed, err:", err.Error())
	}

	for i := 0; i < len(netInterfaces); i++ {
		if (netInterfaces[i].Flags & net.FlagUp) != 0 {
			addrs, _ := netInterfaces[i].Addrs()

			for _, address := range addrs {
				if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
					if ipnet.IP.To4() != nil {
						return ipnet.IP.String()
					}
				}
			}
		}

	}
	return ""
}
func IsContain(items []string, item string) bool {
	for _, eachItem := range items {
		if eachItem == item {
			return true
		}
	}
	return false
}
func CopyFile(srcFilePath string, dstFilePath string) error {
	srcFile, err := os.Open(srcFilePath)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	distFile, err := os.Create(dstFilePath)
	if err != nil {
		return err
	}
	defer distFile.Close()

	var tmp = make([]byte, 1024*4)
	for {
		n, err := srcFile.Read(tmp)
		distFile.Write(tmp[:n])
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
	}
}
func insertSlice(a []string, i int, str string) []string {
	a = append(a, "")    // 切片扩展1个空间
	copy(a[i+1:], a[i:]) // a[i:]向后移动1个位置
	a[i] = str           // 设置新添加的元素
	return a
}
func GetPath() string {
	file, _ := exec.LookPath(os.Args[0])
	path, _ := filepath.Abs(file)
	index := strings.LastIndex(path, string(os.PathSeparator))
	return path[:index]
}
